using System;
using System.Collections;

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;

namespace Org.BouncyCastle.Crypto.Macs
{
	/**
	* HMAC implementation based on RFC2104
	*
	* H(K XOR opad, H(K XOR ipad, text))
	*/
	public class HMac
		: IMac
	{
		private const byte IPAD = (byte)0x36;
		private const byte OPAD = (byte)0x5C;

		private readonly IDigest digest;
		private readonly int digestSize;
		private readonly int blockLength;

		private readonly byte[] inputPad;
		private readonly byte[] outputPad;

		public HMac(
			IDigest digest)
		{
			this.digest = digest;
			this.digestSize = digest.GetDigestSize();
			this.blockLength = digest.GetByteLength();
			this.inputPad = new byte[blockLength];
			this.outputPad = new byte[blockLength];
		}

		public string AlgorithmName
		{
			get { return digest.AlgorithmName + "/HMAC"; }
		}

		public IDigest GetUnderlyingDigest()
		{
			return digest;
		}

		public void Init(
			ICipherParameters parameters)
		{
			digest.Reset();

			byte[] key = ((KeyParameter)parameters).GetKey();
			int keyLength = key.Length;

			if (keyLength > blockLength)
			{
				digest.BlockUpdate(key, 0, key.Length);
				digest.DoFinal(inputPad, 0);

				keyLength = digestSize;
			}
			else
			{
				Array.Copy(key, 0, inputPad, 0, keyLength);
			}

			Array.Clear(inputPad, keyLength, blockLength - keyLength);
			Array.Copy(inputPad, 0, outputPad, 0, blockLength);

			xor(inputPad, IPAD);
			xor(outputPad, OPAD);

			// Initialise the digest
			digest.BlockUpdate(inputPad, 0, inputPad.Length);
		}

		public int GetMacSize()
		{
			return digestSize;
		}

		public void Update(
			byte input)
		{
			digest.Update(input);
		}

		public void BlockUpdate(
			byte[] input,
			int inOff,
			int len)
		{
			digest.BlockUpdate(input, inOff, len);
		}

		public int DoFinal(
			byte[] output,
			int outOff)
		{
			byte[] tmp = new byte[digestSize];
			digest.DoFinal(tmp, 0);

			digest.BlockUpdate(outputPad, 0, outputPad.Length);
			digest.BlockUpdate(tmp, 0, tmp.Length);

			int len = digest.DoFinal(output, outOff);

			// Initialise the digest
			digest.BlockUpdate(inputPad, 0, inputPad.Length);

			return len;
		}

		/**
		* Reset the mac generator.
		*/
		public void Reset()
		{
			// Reset underlying digest
			digest.Reset();

			// Initialise the digest
			digest.BlockUpdate(inputPad, 0, inputPad.Length);
		}

		private static void xor(byte[] a, byte n)
		{
			for (int i = 0; i < a.Length; ++i)
			{
				a[i] ^= n;
			}
		}
	}
}
